
<!DOCTYPE HTML>
<html>
<head>
	<meta charset="utf-8">
	<title>class-swizzling, isa-swizzling and KVO - 乐の博客</title>
	<meta name="author" content="zhidong">

	
	<meta name="description" content="Class-swizzling, Isa-swizzling and KVO 前言 看ReactiveCocoa源码的时候，被RACSwizzleClass卡住了，做了以下研究，并把注释后的代码放在文章最后，如果不想看过程可以直接跳到最后。 RACSwizzleClass中通过判断[obj &hellip;">
	<meta name="keywords" content="iOS,KVO,isa-swizzling,class-swizzling">

  <!-- http://t.co/dKP3o1e -->
  <meta name="HandheldFriendly" content="True">
  <meta name="MobileOptimized" content="320">
  <meta name="viewport" content="width=device-width, initial-scale=1">

	<link href="/atom.xml" rel="alternate" title="乐の博客" type="application/atom+xml">
	
	<link rel="canonical" href="https://zhidong.rochu.cn/blog/2016/03/07/class-swizzling/">
	<link href="/favicon.png" rel="shortcut icon">
	<link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css">
	<!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
	<script src="/javascripts/libs/jquery.min.js"></script>
	<!-- <link href='//fonts.googleapis.com/css?family=Open+Sans:400italic,400,700' rel='stylesheet' type='text/css'> -->
	
</head>


<body>
	<div class="container">
		<div class="left-col">
			<div class="intrude-less">
			<header id="header" class="inner"><div class="profilepic">	
	<script src="/javascripts/md5.js"></script>
	<script type="text/javascript">
		document.write("<img src='/images/pingtouge.png' alt='Profile Picture' style='width: 160px;' />");
	</script>
</div>
<h1><a href="/">乐の博客</a></h1>
<p class="subtitle">行而不思则罔，思而不行则怠</p>
<nav id="main-nav"><ul>
	<li><a href="/">Blog</a></li>
	<li><a href="/about">About</a></li>
	<li><a href="/portfolio">Portfolio</a></li>
	<li><a href="/archives">Archive</a></li>
</ul>
</nav>
<nav id="sub-nav">
	<div class="social">
		
		<a class="weibo" href="http://www.weibo.com/agdsdl" title="Weibo">Weibo</a>
		
		
		
		
		
		<a class="github" href="https://github.com/agdsdl" title="GitHub">GitHub</a>
		
		
		
		
		
		
		
		<a class="rss" href="/atom.xml" title="RSS">RSS</a>
		
	</div>
</nav>
<p class="description">知冬的后花园</p>
</header>				
			</div>
		</div>	
		<div class="mid-col">
			
				
			
			<div class="mid-col-container">
				<div id="content" class="inner"><article class="post" itemscope itemtype="http://schema.org/BlogPosting">
	<h1 class="title" itemprop="name">Class-swizzling, Isa-swizzling and KVO</h1>
	<div class="entry-content" itemprop="articleBody"><h2>前言</h2>

<p>看<code>ReactiveCocoa</code>源码的时候，被<code>RACSwizzleClass</code>卡住了，做了以下研究，并把注释后的代码放在文章最后，如果不想看过程可以直接跳到最后。</p>

<p><code>RACSwizzleClass</code>中通过判断<code>[obj class]</code>和 <code>object_getClass(obj)</code>是否相同来执行不同的逻辑。
然而<code>[obj class]</code>和<code>object_getClass(obj)</code>有什么区别？他们不同到底意味着什么？</p>

<h2>class和object_getClass</h2>

<p>为此查阅了objc runtime的源代码，并整理了相关代码：<a href="https://gist.github.com/agdsdl/a22666c8f64fed0dbbf5">get class 相关代码</a></p>

<p>结论:</p>

<ul>
<li>对于一个普通的obj（不是class），<code>[obj class]</code>和 <code>object_getClass(obj)</code>会返回一样的结果，就是该对象所属的类。</li>
<li>对于一个class，<code>[aclass class]</code>还是会返回该class，而<code>object_getClass(aclass)</code>会溯本归源返回aclass的<code>isa</code>，一般是返回该类的<code>meta class</code></li>
</ul>


<p>对象的<code>isa</code>链会一直指向哪里?见下图:</p>

<p><img src="/images/class-diagram.jpg" alt="objc 关系图" /></p>

<p>(图片来自<a href="http://blog.devtang.com/2013/10/15/objective-c-object-model/">http://blog.devtang.com/2013/10/15/objective-c-object-model/</a>)</p>

<p>图中的<code>Root class</code>一般是<code>NSObject</code>，当你不停的调用<code>object_getClass</code>，你最终会获取到<code>NSObject</code>的<code>meta class</code>。</p>

<h2>isa-swizzling</h2>

<p>既然对于一个普通的obj（不是class），<code>[obj class]</code>和 <code>object_getClass(obj)</code>会返回一样的结果，那么<code>RACSwizzleClass</code>为什么要做相等性判断？</p>

<p>在苹果的文档中稍稍提到了一些：
<a href="https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOImplementation.html">Key-Value Observing Implementation Details</a></p>

<p>简单的说：</p>

<ul>
<li>系统的KVO，是用<code>isa-swizzling</code>实现的。</li>
<li><code>isa</code>指针，指向对象所属的类，类里面存储的是方法列表及其他一些信息。</li>
<li>当你给一个对象添加了observer之后，系统会修改该对象的<code>isa</code>指针，使其指向一个中间类（中间类重写了setting方法以实现KVO），这时的<code>isa</code>，就不是指向对象实际的类了。</li>
</ul>


<p>所以，你应该用<code>class</code>方法(因为系统同时重写了class方法使其返回对象原来的类)，而不是<code>isa</code>(object_getClass)来取得对象所属的类。</p>

<!-- more -->


<p>写了个小小的测试程序来验证：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>- (void)test{
</span><span class='line'>    //Now create an instance 'myobj' of class 'MyObject'
</span><span class='line'>    NSLog(@"Now create an instance 'myobj' of class 'MyObject'");
</span><span class='line'>    MyObject *myobj = [[MyObject alloc] init];
</span><span class='line'>    Class aclass = [myobj class];
</span><span class='line'>    NSLog(@"[myobj class] returns:%@(%p)", aclass, aclass);
</span><span class='line'>    Class aclass2 = object_getClass(myobj);
</span><span class='line'>    NSLog(@"object_getClass(myobj) returns:%@(%p)", aclass2, aclass2);
</span><span class='line'>    Class aclass3 = object_getClass([myobj class]);
</span><span class='line'>    NSLog(@"object_getClass([myobj class]) returns:%@(%p)", aclass3, aclass3);
</span><span class='line'>
</span><span class='line'>    //Now recursively call 'class'
</span><span class='line'>    NSLog(@"Now recursively call 'class'");
</span><span class='line'>    id obj = myobj;
</span><span class='line'>    for (int i=0; i&lt;4; i++) {
</span><span class='line'>        Class aclass = [obj class];
</span><span class='line'>        obj = aclass;
</span><span class='line'>        NSLog(@"pass%d [obj class] returns %@(%p)", i+1, aclass, aclass);
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>    // Now recursively call 'object_getClass'.
</span><span class='line'>    NSLog(@"Now recursively call 'object_getClass'");
</span><span class='line'>    obj = myobj;
</span><span class='line'>    for (int i=0; i&lt;4; i++) {
</span><span class='line'>        Class aclass = object_getClass(obj);
</span><span class='line'>        obj = aclass;
</span><span class='line'>        NSLog(@"pass%d object_getClass(obj) returns %@(%p)", i+1, aclass, aclass);
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>    
</span><span class='line'>    // Now add KVO to myobj.
</span><span class='line'>    NSLog(@"Now add KVO to myobj.");
</span><span class='line'>    [myobj addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
</span><span class='line'>    NSLog(@"After KVO. [myobj class] returns %@(%p)", [myobj class], [myobj class]);
</span><span class='line'>    NSLog(@"After KVO. object_getClass(myobj) returns %@(%p)", object_getClass(myobj), object_getClass(myobj));
</span><span class='line'>
</span><span class='line'>    myobj.title = @"Hello, this is new title!";
</span><span class='line'>    
</span><span class='line'>    // Now remove KVO of myobj.
</span><span class='line'>    NSLog(@"Now remove KVO of myobj.");
</span><span class='line'>    [myobj removeObserver:self forKeyPath:@"title"];
</span><span class='line'>    NSLog(@"KVO removed. [myobj class] returns %@(%p)", [myobj class], [myobj class]);
</span><span class='line'>    NSLog(@"KVO removed. object_getClass(myobj) returns %@(%p)", object_getClass(myobj), object_getClass(myobj));
</span><span class='line'>}
</span><span class='line'>- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary&lt;NSString *,id&gt; *)change context:(void *)context{
</span><span class='line'>    if ([keyPath isEqualToString:@"title"]) {
</span><span class='line'>        NSLog(@"KVO title changed:%@", [change objectForKey:NSKeyValueChangeNewKey]);
</span><span class='line'>    }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>运行结果：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>2016-03-07 17:19:09.702 testOC[3925:131380] Now create an instance 'myobj' of class 'MyObject'
</span><span class='line'>2016-03-07 17:19:09.704 testOC[3925:131380] [myobj class] returns:MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.704 testOC[3925:131380] object_getClass(myobj) returns:MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.704 testOC[3925:131380] object_getClass([myobj class]) returns:MyObject(0x1065200f8)
</span><span class='line'>2016-03-07 17:19:09.705 testOC[3925:131380] Now recursively call 'class'
</span><span class='line'>2016-03-07 17:19:09.705 testOC[3925:131380] pass1 [obj class] returns MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.705 testOC[3925:131380] pass2 [obj class] returns MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.705 testOC[3925:131380] pass3 [obj class] returns MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.705 testOC[3925:131380] pass4 [obj class] returns MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.705 testOC[3925:131380] Now recursively call 'object_getClass'
</span><span class='line'>2016-03-07 17:19:09.706 testOC[3925:131380] pass1 object_getClass(obj) returns MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.706 testOC[3925:131380] pass2 object_getClass(obj) returns MyObject(0x1065200f8)
</span><span class='line'>2016-03-07 17:19:09.706 testOC[3925:131380] pass3 object_getClass(obj) returns NSObject(0x106d7b198)
</span><span class='line'>2016-03-07 17:19:09.712 testOC[3925:131380] pass4 object_getClass(obj) returns NSObject(0x106d7b198)
</span><span class='line'>2016-03-07 17:19:09.712 testOC[3925:131380] Now add KVO to myobj.
</span><span class='line'>2016-03-07 17:19:09.713 testOC[3925:131380] After KVO. [myobj class] returns MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.713 testOC[3925:131380] After KVO. object_getClass(myobj) returns NSKVONotifying_MyObject(0x7fc66b61f000)
</span><span class='line'>2016-03-07 17:19:09.713 testOC[3925:131380] KVO title changed:Hello, this is new title!
</span><span class='line'>2016-03-07 17:19:09.713 testOC[3925:131380] Now remove KVO of myobj.
</span><span class='line'>2016-03-07 17:19:09.714 testOC[3925:131380] KVO removed. [myobj class] returns MyObject(0x106520120)
</span><span class='line'>2016-03-07 17:19:09.714 testOC[3925:131380] KVO removed. object_getClass(myobj) returns MyObject(0x106520120)</span></code></pre></td></tr></table></div></figure>


<h2>总结：</h2>

<p>苹果的<code>isa-swizzling</code>（也就是<code>class-swizzling</code>）会更改对象的<code>isa</code>指针，并且会重写<code>class</code>方法以隐藏class已经改变的事实。。。</p>

<p>如果有兴趣，这还一篇文档教你怎么自己实现KVO：
<a href="http://tech.glowing.com/cn/implement-kvo/">如何自己动手实现 KVO</a></p>

<p>在搜索的时候，搜到了stackoverflow上类似的一个问题，然而点赞最多的回答并不准确，我在后面附上了准确的回答：
<a href="http://stackoverflow.com/questions/15906130/object-getclassobj-and-obj-class-give-different-results/35037484#35037484">object_getClass(obj) and [obj class] give different results</a></p>

<h2>附注释后代码</h2>

<p>现在<code>RACSwizzleClass</code>的代码就可以读得通了，附上注释如下：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>// file NSObject+RACSelectorSignal.m
</span><span class='line'>static Class RACSwizzleClass(NSObject *self) {
</span><span class='line'>  Class statedClass = self.class; // 声明的类
</span><span class='line'>  Class baseClass = object_getClass(self); // 实际指向的类
</span><span class='line'>  NSString *className = NSStringFromClass(baseClass);
</span><span class='line'>
</span><span class='line'>  if ([className hasSuffix:RACSubclassSuffix]) {
</span><span class='line'>      return baseClass; // 已经swizzle过了
</span><span class='line'>  } else if (statedClass != baseClass) {
</span><span class='line'>    // 正如下面的注释，该对象估计被类似KVO之类的isa-swizzle过了，只能swizzle它的ForwardInvocation方法了
</span><span class='line'>
</span><span class='line'>      // If the class is already lying about what it is, it's probably a KVO
</span><span class='line'>      // dynamic subclass or something else that we shouldn't subclass
</span><span class='line'>      // ourselves.
</span><span class='line'>      //
</span><span class='line'>      // Just swizzle -forwardInvocation: in-place. Since the object's class
</span><span class='line'>      // was almost certainly dynamically changed, we shouldn't see another of
</span><span class='line'>      // these classes in the hierarchy.
</span><span class='line'>      //
</span><span class='line'>      // Additionally, swizzle -respondsToSelector: because the default
</span><span class='line'>      // implementation may be ignorant of methods added to this class.
</span><span class='line'>      @synchronized (swizzledClasses()) {
</span><span class='line'>          if (![swizzledClasses() containsObject:className]) {
</span><span class='line'>              RACSwizzleForwardInvocation(baseClass);
</span><span class='line'>              RACSwizzleRespondsToSelector(baseClass);
</span><span class='line'>              [swizzledClasses() addObject:className];
</span><span class='line'>          }
</span><span class='line'>      }
</span><span class='line'>
</span><span class='line'>      return baseClass;
</span><span class='line'>  }
</span><span class='line'>
</span><span class='line'>  // 没有isa-swizzle过的，直接isa-swizzle
</span><span class='line'>  const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String;
</span><span class='line'>  Class subclass = objc_getClass(subclassName);
</span><span class='line'>
</span><span class='line'>  if (subclass == nil) {
</span><span class='line'>      subclass = [RACObjCRuntime createClass:subclassName inheritingFromClass:baseClass];
</span><span class='line'>      if (subclass == nil) return nil;
</span><span class='line'>
</span><span class='line'>      RACSwizzleForwardInvocation(subclass);
</span><span class='line'>      RACSwizzleRespondsToSelector(subclass);
</span><span class='line'>      objc_registerClassPair(subclass);
</span><span class='line'>  }
</span><span class='line'>
</span><span class='line'>  object_setClass(self, subclass);
</span><span class='line'>  return subclass;
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>

</div>

</article>
<!-- 
	<div class="share">
	<div class="addthis_toolbox addthis_default_style addthis_16x16_style">
	
	
	
	
	
	<a class="addthis_button_compact"></a>
	<a class="addthis_counter addthis_bubble_style"></a>
	</div>
  <script type="text/javascript" src="http://s7.addthis.com/js/300/addthis_widget.js#pubid="></script>
</div>

 -->


<section id="comment">
    <h1 class="title">Comments</h1>
    <div id="gitalk-container"></div>
    <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
    <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
    <script>
    const gitalk = new Gitalk({
    clientID: '29bbf743e30b99394771',
    clientSecret: '41e4b7f1aa79742fc459f2971ad51596df5e4dda',
    repo: 'agdsdl.github.io',
    owner: 'agdsdl',
    admin: ['agdsdl'],
    id: location.pathname,      // Ensure uniqueness and length less than 50
    distractionFreeMode: false  // Facebook-like distraction free mode
    })

    gitalk.render('gitalk-container')
    </script>
</section></div>
			</div>
			<footer id="footer" class="inner">Copyright &copy; 2019

    zhidong

<br>
Powered by Octopress.

Design credit: <a href="http://shashankmehta.in/archive/2012/greyshade.html">Shashank Mehta</a></footer>
			<script src="/javascripts/slash.js"></script>
<!-- <script src="/javascripts/jquery.fancybox.pack.js"></script>
<script type="text/javascript">
(function($){
	$('.fancybox').fancybox();
})(jQuery);
</script> Delete or comment this line to disable Fancybox -->




	<script type="text/javascript">
		var _gaq = _gaq || [];
		_gaq.push(['_setAccount', 'UA-65560025-1']);
		_gaq.push(['_trackPageview']);

		(function() {
			var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
			ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
			var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
		})();
	</script>



		</div>
	</div>
</body>
</html>
